#ifndef _SLEIPNER_OBJECT_SET_H_
#define _SLEIPNER_OBJECT_SET_H_

#include <vector>
#include <string>
#include <algorithm>
#include "math/vec2.h"
#include <stddef.h>

template<class T>
class	SLPObjectSet
{
public:
	SLPObjectSet(bool	ShouldDelete = true) :
		m_ShouldDelete(ShouldDelete),
		m_RenderDebug(true)
		{

		}
	~SLPObjectSet()
	{
		Clear();
	}
	void				SetDelete(bool b)
	{
		m_ShouldDelete = b;
	}
	void				Clear()
	{
		if (m_ShouldDelete)
		{
			for (unsigned int i=0; i<m_Objects.size(); i++)
			{
				delete m_Objects[i];
			}
		}
		m_Objects.resize(0);
	}

	T*				NearestObj(const math::Vec2& P)
	{
		return NearestObjEx(P, -1);	// all valid
	}	
	T*				NearestObjEx(const math::Vec2& P, int Type)
	{
		float	Nearest = (float)10e10;
		T		pNearest = NULL;
		for (unsigned int i=0; i<m_Objects.size(); i++)
		{
			if (Type >= 0 && m_Objects[i]->GetType() != Type) continue;
			math::Vec2	P2 = m_Objects[i]->GetPos2D();
			float	d = (P2-P).Magnitude();
			if (d < Nearest)
			{
				Nearest = d;
				pNearest = m_Objects[i];
			}
		}
		return pNearest;
	}	
	T*				GetObj(const math::Vec2& P, int Dir = 1)
	{
		if (!m_RenderDebug) return NULL;	// disable picking!
		if (Dir < 0)
		{
			for (int i = (int)(m_Objects.size()-1); i >= 0; i--)
			{
				if (m_Objects[i]->IsInside(P))	return m_Objects[i];
			}
		} else
		{
			for (unsigned int i = 0, c=(unsigned int)m_Objects.size(); i <c ; i++)
			{
				if (m_Objects[i]->IsInside(P))	return m_Objects[i];
			}
		}
		return NULL;
	}	
	T*				GetObjEx(const math::Vec2& P, int Type, int Dir = 1)	
	{
		// always get in reverse rendering order
		if (m_Objects.empty()) return NULL;

		if (Dir < 0)
		{
			for (int i = (int)(m_Objects.size()-1); i >= 0; i--)
			{
				if (m_Objects[i]->GetType() == Type && m_Objects[i]->IsInside(P))	return m_Objects[i];
			}
		} else
		{
			for (unsigned int i = 0, c=(unsigned int)m_Objects.size(); i <c ; i++)
			{
				if (m_Objects[i]->GetType() == Type && m_Objects[i]->IsInside(P))	return m_Objects[i];
			}
		}

		return NULL;

	}
	void				FindAllAt(const math::Vec2& P, std::vector<T>& ToSet)
	{
		for (unsigned int i=0; i<m_Objects.size(); i++)
		{
			if (m_Objects[i]->IsInside(P))
			{
				ToSet.push_back(m_Objects[i]);
			}
		}
	}

	void				FindAllAt(const math::Vec2& From, const math::Vec2& To,int Type, std::vector<T>& ToSet)
	{
		for (unsigned int i=0; i<m_Objects.size(); i++)
		{
			if (m_Objects[i]->GetType() == Type)
				if (m_Objects[i]->IsInside(From,To))
						ToSet.push_back(m_Objects[i]);
				
		}
	}

	void				FindAllOf(int	Type, std::vector<T>& ToSet)
	{
		for (unsigned int i=0; i<m_Objects.size(); i++)
		{
			if (m_Objects[i]->GetType() == Type )
			{
				ToSet.push_back(m_Objects[i]);
			}
		}
	}
	T*					FindFirstOf(int	Type)
	{
		for (unsigned int i=0; i<m_Objects.size(); i++)
		{
			if (m_Objects[i]->GetType() == Type )
			{
				return m_Objects[i];
			}
		}
		return NULL;
	}
	T*					FindByName(const std::string& Name)
	{
		unsigned int Key = num::CRC32::FastHash(Name);
		for (unsigned int i=0; i<m_Objects.size(); i++)
		{
			if (num::CRC32::FastHash(m_Objects[i]->GetName())==Key) return m_Objects[i];
		}
		return NULL;
	}
	bool				EraseObj(const math::Vec2& P)
	{
		T* pObj = GetObj(P);
		if (pObj)
		{
			EraseObj(pObj);
			return true;
		}
		return false;
	}

	bool				EraseObj(T*	pObj, bool Delete = true)
	{
		if (!pObj) return false;
		for (unsigned int i=0; i<m_Objects.size(); i++)
		{
			if (m_Objects[i] == pObj)
			{
				if (m_ShouldDelete && Delete)
				{
					delete m_Objects[i];
				}
				m_Objects.erase(m_Objects.begin()+i);
				return true;
			}
		}
		return false;
	}

	void				AddObj(T*		pObj)
	{
		// check to make sure it's not a duplicate?
		m_Objects.push_back(pObj);
	}

	void				Tick(float dt)
	{
		for (unsigned int i=0; i<m_Objects.size(); i++)
		{
			m_Objects[i]->Tick(dt);
		}
	}
	void				RemoveDeadToList(SLPObjectSet<T>& List)
	{
		for (int i=int(m_Objects.size())-1; i>=0; i--)
		{
			T*	pObj = m_Objects[i];
			if (pObj->ShouldDelete())
			{
				if (pObj->CanDelete())
				{
					List.AddObj(pObj);
				}
				m_Objects.erase(m_Objects.begin()+i);
			}
		}
	}
	void				RemoveDead()
	{
		for (int i=int(m_Objects.size())-1; i>=0; i--)
		{
			T*	pObj = m_Objects[i];
			if (pObj->ShouldDelete())
			{
				if (pObj->CanDelete())
				{
					delete 	pObj;
				}
				m_Objects.erase(m_Objects.begin()+i);
			}
		}
	}
	void				Render(const math::Vec2& Offset, float Scale)
	{
		for (unsigned int i=0; i<m_Objects.size(); i++)
		{
			if (m_Objects[i]->IsVisible(Offset, Scale, 1))
				m_Objects[i]->Render(Offset, Scale);
		}
	}
	void				RenderDebug(const math::Vec2& Offset, float Scale)
	{
		if (!m_RenderDebug) return;
		for (unsigned int i=0; i<m_Objects.size(); i++)
		{
			m_Objects[i]->RenderDebug(Offset, Scale);
		}
	}
	// mimic std::vector
	size_t				size()
	{
		return m_Objects.size();
	}
	inline
	T*&					operator[] (size_t	i)  
	{
		return m_Objects[i];
	} 
	std::vector<T*>			m_Objects;
	void					ShouldRenderDebug(bool b) { m_RenderDebug = b;};
	bool					IsRenderingDebug(bool b) { return m_RenderDebug;};
private:
	bool					m_ShouldDelete;
	bool					m_RenderDebug;
};

#endif // _SLEIPNER_OBJECT_SET_H_